#include "datafield.h"
#include <xfs/xfs.h>

DataField::DataField() {} 
DataField::~DataField() {}

void DataField::init(DynType* dt, const DynObject& dflt_value) 
{ 
  this->value_dyntype = dt;
  this->dflt_value = dflt_value; 
}

DynType* DataField::get_value_dyntype() const
{
  return value_dyntype;
}

const DynObject& DataField::get_dflt_value() const 
{ 
  return this->dflt_value; 
}

const vector<data_index_t>& DataField::get_shape() const 
{ 
  return this->shape; 
}

void DataField::dump() const
{
  this->print(cout);
}

// ...............................................

PrintCB::PrintCB(ostream& out, const DataField* field) 
{
  this->out = &out;
  this->field = field;
}

void PrintCB::do_it(const vector<data_index_t>& coor) 
{
  if (this->field->is_none_value(coor) == false) {
    DynObject value;
    this->field->get_value(coor, &value);
    print_coor(*out, coor); 
    *out << ": " << value << endl;
  }
}

// ...............................................

MMapDataField::MMapDataField() 
{
  data = 0;
}

MMapDataField::MMapDataField(DynType* value_dyntype,
			     const vector<data_index_t>& max_shape,
			     const char* fn,
			     bool open_writable)
{     
  init__(value_dyntype, max_shape, fn, open_writable);
}

void MMapDataField::init__(DynType* value_dyntype,
			   const vector<data_index_t>& max_shape,
			   const char* fn,
			   bool open_writable)
{
  this->value_dyntype = value_dyntype;
  this->max_shape = max_shape;
  this->shape.resize(max_shape.size());
  size_t max_shape_size=1;
  for (size_t i_dim = 0;
       i_dim < this->max_shape.size(); 
       i_dim++) {
    max_shape_size *= this->max_shape[i_dim];
  }
  max_shape_size += 1;
  size_t total_mmap_size = max_shape_size * value_dyntype->get_size();
  mmap_data.open_mmap(fn, open_writable, total_mmap_size);
  this->data = (char*)mmap_data.mmap_addr;
}

void MMapDataField::print(ostream& out) const 
{
  PrintCB cb(out, this);
  NDimRange tr(this->shape, this->max_shape);
  tr.traverse(&cb);
}

string MMapDataField::get_filename() const 
{
  return mmap_data.fn;
}

void apply_xor_mask(char* dest, const membuf_t& none_rep) {
  for (size_t i = 0; i < none_rep.buf_len; i++) {
    dest[i] ^= none_rep.buf[i];
  }
}

bool MMapDataField::is_none_value(const vector<data_index_t>& data_coor) const 
{
  size_t direct_index = get_direct_index(max_shape, data_coor);
  membuf_t v_buf;
  v_buf.buf = &data[direct_index * value_dyntype->get_size()];
  v_buf.buf_len = value_dyntype->get_size();
  bool ret = true;
  for (size_t i = 0; i < v_buf.buf_len; i++) {
    if (v_buf.buf[i] != '\0') {
      ret = false;
      break;
    }
  }
  return ret;
}

void MMapDataField::get_value(const vector<data_index_t>& data_coor, 
			      DynObject* v) const 
{
  v->set_o_buf(value_dyntype);
  size_t direct_index = get_direct_index(max_shape, data_coor);
  membuf_t v_buf;
  v_buf.buf = &data[direct_index * value_dyntype->get_size()];
  v_buf.buf_len = value_dyntype->get_size();
  memcpy(v->get_o_buf().buf, v_buf.buf, v_buf.buf_len);
  apply_xor_mask(v->get_o_buf().buf, v->get_dyntype()->none_rep);
}

void MMapDataField::set_value(const vector<data_index_t>& data_coor,
			      const DynObject& value)
{
  size_t direct_index = get_direct_index(max_shape, data_coor);
  membuf_t dest;
  dest.buf = &data[direct_index * value_dyntype->get_size()];
  dest.buf_len = value_dyntype->get_size();
  memcpy(dest.buf, value.get_o_buf().buf, value.get_o_buf().buf_len);
  apply_xor_mask(dest.buf, value.get_dyntype()->none_rep);
}

void MMapDataField::allocate_indexes(size_t i_dim, size_t n, 
				     vector<data_index_t>* new_indexes)
{
  data_index_t i_dim_size = this->shape[i_dim];
  for (size_t i = 0; i < n; i++) {
    new_indexes->push_back(i_dim_size + i);
  }
  this->shape[i_dim] += n;
}

void MMapDataField::delete_indexes(size_t i_dim, 
				   const vector<data_index_t>& del_indexes)
{
  // no reclaiming
}

void MMapDataField::save() const
{
  //ostrstream metadata_s(mmap_data.mmap_metadata, 4 * 1024);
  ostringstream metadata_s;
  value_dyntype->save(metadata_s); metadata_s << endl;
  copy(max_shape.begin(), max_shape.end(), 
       ostream_iterator<size_t>(metadata_s, ","));
  metadata_s << endl;
  copy(this->shape.begin(), this->shape.end(),
       ostream_iterator<size_t>(metadata_s, ","));
  metadata_s << endl;
  if (metadata_s.str().size() < 4 * 1024) {
    strcpy(mmap_data.mmap_metadata, metadata_s.str().c_str());
  } else {
    throw Error(__FILE__, __LINE__, "DataField::save: metadata is too big");
  }
}

void MMapDataField::load(const string& fn, bool open_writable)
{
  mmap_data.open_mmap(fn.c_str(), open_writable, 0);
  this->data = (char*)mmap_data.mmap_addr;
  
  istringstream metadata_in(string(mmap_data.mmap_metadata, 4 * 1024));
  string value_dyntypename = DynTypeManager::get()->load_and_register_dyntype(metadata_in);
  this->value_dyntype = DynTypeManager::get()->get_dyntype(value_dyntypename);

  string max_shape_line, shape_line;
  metadata_in >> max_shape_line >> shape_line;
  
  istringstream max_shape_line_s(max_shape_line);
  vector<size_t> read_max_shape;
  char n_buf[128];
  while (max_shape_line_s.getline(n_buf, sizeof n_buf, ',')) {
    read_max_shape.push_back(atoi(n_buf));
  }
  this->max_shape = read_max_shape;
  
  istringstream shape_line_s(shape_line);
  vector<size_t> read_shape;    
  while (shape_line_s.getline(n_buf, sizeof n_buf, ',')) {
    read_shape.push_back(atoi(n_buf));
  }
  this->shape = read_shape;
}

// ....................................................................

static size_t metadata_size = 4 * 1024; // metadata 4k

FileDataField::FileDataField() 
{
    fd = -1;
    nof_set_values = 0;
}

FileDataField::FileDataField(DynType* value_dyntype,
			     const vector<data_index_t>& max_shape,
			     const char* fn,
			     bool open_writable)
{     
    init__(value_dyntype, max_shape, fn, open_writable);
}

void FileDataField::init__(DynType* value_dyntype,
			   const vector<data_index_t>& max_shape,
			   const char* fn,
			   bool open_writable)
{
  this->value_dyntype = value_dyntype;
  this->nof_set_values = 0;
  this->max_shape = max_shape;
  this->shape.resize(max_shape.size());
  size_t max_shape_size=1;
  for (size_t i_dim = 0;
       i_dim < this->max_shape.size(); 
       i_dim++) {
    max_shape_size *= this->max_shape[i_dim];
  }
  max_shape_size += 1;
  size_t total_mmap_size = max_shape_size * value_dyntype->get_size();

  this->fn = fn;
  this->fd = open(this->fn.c_str(), open_writable ? O_CREAT | O_RDWR | O_NONBLOCK : O_RDONLY, 0600);

#if 0
  xfs_flock64_t   fl;
  fl.l_whence = 0;
  fl.l_start = 1;
  fl.l_len = (off64_t)(1024 * 1024 * 1024); /* 1Gb */
  int err = xfsctl(fn, fd, XFS_IOC_RESVSP, &fl);
  if (err < 0) {
      perror("allocsp: ");
      printf("XFS_IOC_RESVSP64 failed\n");
      close(fd);
      exit(1);
  }
#endif
  ftruncate(this->fd, total_mmap_size);
}

void FileDataField::print(ostream& out) const 
{
  PrintCB cb(out, this);
  NDimRange tr(this->shape, this->max_shape);
  tr.traverse(&cb);
}

string FileDataField::get_filename() const 
{
  return this->fn;
}

bool FileDataField::is_none_value(const vector<data_index_t>& data_coor) const 
{
  size_t direct_index = get_direct_index(max_shape, data_coor);
  membuf_t v_buf;
  void* bb = alloca(value_dyntype->get_size());
  v_buf.buf = (char*)bb;
  v_buf.buf_len = value_dyntype->get_size();
  pread(fd, v_buf.buf, v_buf.buf_len, 
	direct_index * value_dyntype->get_size() + metadata_size);
  bool ret = true;
  for (size_t i = 0; i < v_buf.buf_len; i++) {
    if (v_buf.buf[i] != '\0') {
      ret = false;
      break;
    }
  }
  return ret;
}

void FileDataField::get_value(const vector<data_index_t>& data_coor, 
			      DynObject* v) const 
{
  v->set_o_buf(value_dyntype);
  size_t direct_index = get_direct_index(max_shape, data_coor);
  pread(fd, v->get_o_buf().buf, value_dyntype->get_size(),
	direct_index * value_dyntype->get_size() + metadata_size);
  apply_xor_mask(v->get_o_buf().buf, v->get_dyntype()->none_rep);
}

void FileDataField::set_value(const vector<data_index_t>& data_coor,
			      const DynObject& value)
{
  membuf_t dest;
  dest.buf = (char*)alloca(value.get_o_buf().buf_len);
  dest.buf_len = value.get_o_buf().buf_len;
  memcpy(dest.buf, value.get_o_buf().buf, dest.buf_len);
  apply_xor_mask(dest.buf, value.get_dyntype()->none_rep);
  size_t direct_index = get_direct_index(max_shape, data_coor);
  pwrite(fd, dest.buf, dest.buf_len, direct_index * value_dyntype->get_size() + metadata_size);
  nof_set_values++;
}

void FileDataField::allocate_indexes(size_t i_dim, size_t n, 
				     vector<data_index_t>* new_indexes)
{
  data_index_t i_dim_size = this->shape[i_dim];
  for (size_t i = 0; i < n; i++) {
    new_indexes->push_back(i_dim_size + i);
  }
  this->shape[i_dim] += n;
}

void FileDataField::delete_indexes(size_t i_dim, 
				   const vector<data_index_t>& del_indexes)
{
  // no reclaiming
}

void FileDataField::save() const
{
  ostringstream metadata_s;
  value_dyntype->save(metadata_s); metadata_s << endl;
  copy(max_shape.begin(), max_shape.end(), 
       ostream_iterator<size_t>(metadata_s, ","));
  metadata_s << endl;
  copy(this->shape.begin(), this->shape.end(),
       ostream_iterator<size_t>(metadata_s, ","));
  metadata_s << endl;
  metadata_s << nof_set_values << endl;
  if (metadata_s.str().size() < 4 * 1024) {
      pwrite(this->fd, metadata_s.str().c_str(), metadata_s.str().size(), 0);
  } else {
    throw Error(__FILE__, __LINE__, "DataField::save: metadata is too big");
  }

  close(fd);
}

void FileDataField::load(const string& fn, bool open_writable)
{
    this->fn = fn;
    this->fd = open(this->fn.c_str(), open_writable ? O_RDWR : O_RDONLY);
  
    char* metadata_buf = (char*)alloca(metadata_size);
    pread(fd, metadata_buf, metadata_size, 0);
    istringstream metadata_in(string(metadata_buf, metadata_size));
    string value_dyntypename = DynTypeManager::get()->load_and_register_dyntype(metadata_in);
    this->value_dyntype = DynTypeManager::get()->get_dyntype(value_dyntypename);
    
    string max_shape_line, shape_line;
    metadata_in >> max_shape_line >> shape_line;
    
    istringstream max_shape_line_s(max_shape_line);
    vector<size_t> read_max_shape;
    char n_buf[128];
    while (max_shape_line_s.getline(n_buf, sizeof n_buf, ',')) {
	read_max_shape.push_back(atoi(n_buf));
    }
    this->max_shape = read_max_shape;
    
    istringstream shape_line_s(shape_line);
    vector<size_t> read_shape;    
    while (shape_line_s.getline(n_buf, sizeof n_buf, ',')) {
	read_shape.push_back(atoi(n_buf));
    }
    this->shape = read_shape;
}
